package ar.uba.fi.tonyvaliente.documents;

import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * Representa un filtro que aplica el operador OR sobre los
 * elementos recuperados por otros dos filtros cualesquiera.
 * Esta implementado de modo tal que no necesite generar
 * una nueva coleccion con los elementos de ambos iteradores
 * @author dmorello
 */
public class DocumentORFilter extends DocumentBinaryOperatorFilter {

	public DocumentORFilter(DocumentFilter filter1, DocumentFilter filter2,
			boolean negated, DocumentHashing docHash) {
		super(filter1, filter2, negated, docHash);
	}

	/**
	 * Crea un ResultIterator capaz de aplicar el operador OR
	 * sobre los resultados devueltos por los ResultIterators
	 * correspondientes a los filtros asociados a esta instancia
	 */
	@Override
	protected DocumentQueryResultIterator createResultIterator(int totalDocumentos, DocumentIndex index) {
		DocumentQueryResultIterator it1 = filter1.createResultIterator(totalDocumentos, index);
		DocumentQueryResultIterator it2 = filter2.createResultIterator(totalDocumentos, index);
		return new DocumentORResultIterator(it1, it2, totalDocumentos);
	}
	
	@Override
	public boolean equals(Object o) {
		if (o == this) return true;
		if (o == null) return false;
		if (!(o instanceof DocumentORFilter)) return false;
		return equals((DocumentORFilter)o);
		
	}
	
	public boolean equals(DocumentORFilter f) {
		return this.isNegated() == f.isNegated()
			&& this.filter1.equals(f.filter1)
			&& this.filter2.equals(f.filter2);
	}
	
	@Override
	public int hashCode() {
		return (filter1.hashCode() + "OR" + filter2.hashCode() + isNegated()).hashCode();
	}
	
	@Override
	public String toString() {
		String s = "(" + filter1.toString() + " OR " + filter2.toString() + ")";
		if (isNegated()) {
			return "NOT " + s;
		} else {
			return s;
		}
	}
	
	
	/**
	 * ResultIterator capaz de aplicar el operador OR
	 * sobre los resultados devueltos por los ResultIterators
	 * correspondientes a los filtros asociados a esta instancia
	 */
	private class DocumentORResultIterator extends DocumentQueryResultIterator {
		
		private Iterator<Integer> it1;
		private Iterator<Integer> it2;
		private int id1;
		private int id2;
		private int nextId;
		
		/**
		 * Crea una instancia de la clase que aplica el operador AND
		 * sobre los elementos devueltos por otros dos iteradores
		 * @param it1 un ResultIterator cualquiera
		 * @param it2 un ResultIterator cualquiera
		 * @param totalDocumentos total de documentos del indice invertido
		 */
		public DocumentORResultIterator(DocumentQueryResultIterator it1,
				DocumentQueryResultIterator it2, int totalDocumentos) {
			super(totalDocumentos, it1.index);
			this.it1 = it1;
			this.it2 = it2;
			
			// Seteo estos valores para poder avanzar cada uno
			// de los iteradores cuando sea necesario
			id1 = 0;
			id2 = 0;
			nextId = 0;
		}
		
		public boolean hasNext() {
			return it1.hasNext() || it2.hasNext() || nextId < totalDocumentos;
		}
		
		public Integer next() {
			
			// Verifico si el ultimo elemento dado por it1
			// es el que se devolvio en la llamada anterior a next()
			if (id1 <= totalDocumentos && nextId == id1) {
				// Debo pasar al proximo elemento del iterador (si lo hay)
				if (it1.hasNext()) {
					id1 = it1.next();
				} else {
					// Si no hay, se setea este valor para que no se avance mas
					id1 = totalDocumentos + 1;
				}
			}
			
			// Hago lo mismo con it2
			if (id2 <= totalDocumentos && nextId == id2) {
				if (it2.hasNext()) {
					id2 = it2.next();
				} else {
					id2 = totalDocumentos + 1;
				}
			}
			
			// El menor de los dos elementos tomados sera el siguiente
			nextId = Math.min(id1, id2);
			
			// Verifico si llegue al fin de ambos iteradores
			if (nextId > totalDocumentos) {
				throw new NoSuchElementException("No hay mas elementos en el iterador");
			}
			
			return nextId;
		}
	}

}
